/*-------------------------------------------------------------------------
 *
 * rowtypes.c
 *	  通用复合类型的 I/O 和比较函数。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/utils/adt/rowtypes.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <ctype.h>

#include "access/detoast.h"
#include "access/htup_details.h"
#include "catalog/pg_type.h"
#include "common/hashfn.h"
#include "funcapi.h"
#include "libpq/pqformat.h"
#include "miscadmin.h"
#include "utils/builtins.h"
#include "utils/datum.h"
#include "utils/lsyscache.h"
#include "utils/typcache.h"


/*
 * 用于缓存记录I/O所需元数据的结构
 */
typedef struct ColumnIOData
{
	Oid			column_type;
	Oid			typiofunc;
	Oid			typioparam;
	bool		typisvarlena;
	FmgrInfo	proc;
} ColumnIOData;

typedef struct RecordIOData
{
	Oid			record_type;
	int32		record_typmod;
	int			ncolumns;
	ColumnIOData columns[FLEXIBLE_ARRAY_MEMBER];
} RecordIOData;

/*
 * 结构体用于缓存进行记录比较所需的元数据
 */
typedef struct ColumnCompareData
{
	TypeCacheEntry *typentry;	/* 实际上拥有我们所需的所有内容 */
} ColumnCompareData;

typedef struct RecordCompareData
{
	int			ncolumns;		/* columns[] 的分配长度 */
	Oid			record1_type;
	int32		record1_typmod;
	Oid			record2_type;
	int32		record2_typmod;
	ColumnCompareData columns[FLEXIBLE_ARRAY_MEMBER];
} RecordCompareData;


/*
 * record_in		- 任何复合类型的输入例程。
 */
Datum record_in(PG_FUNCTION_ARGS)
{
	char	   *fc_string = PG_GETARG_CSTRING(0);
	Oid			fc_tupType = PG_GETARG_OID(1);
	int32		fc_tupTypmod = PG_GETARG_INT32(2);
	HeapTupleHeader fc_result;
	TupleDesc	fc_tupdesc;
	HeapTuple	fc_tuple;
	RecordIOData *fc_my_extra;
	bool		fc_needComma = false;
	int			fc_ncolumns;
	int			fc_i;
	char	   *fc_ptr;
	Datum	   *fc_values;
	bool	   *fc_nulls;
	StringInfoData fc_buf;

	check_stack_depth();		/* 对记录类型的列进行递归 */

	/*
	 * 如果我们没有足够的信息来识别目标记录类型，给出友好的错误消息。 
	 * （lookup_rowtype_tupdesc 无论如何会失败，但会有一个不友好的消息。） 
	 * 在普通 SQL 使用中，我们会得到 -1 作为 typmod，因为复合类型和 RECORD 
	 * 在 SQL 层面上没有类型修饰符，因此必须在 RECORD 中失败。 
	 * 然而一些调用者可以提供有效的 typmod，然后我们可以对 RECORD 
	 * 做一些有用的事情。
	 */
	if (fc_tupType == RECORDOID && fc_tupTypmod < 0)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("input of anonymous composite types is not implemented")));

	/*
	 * 这来自复合类型的 pg_type.oid，并在用户表中存储系统 OID， 
	 * 特别是 DatumTupleFields。此 OID 必须在二进制升级中保留。
	 */
	fc_tupdesc = lookup_rowtype_tupdesc(fc_tupType, fc_tupTypmod);
	fc_ncolumns = fc_tupdesc->natts;

	/*
	 * 我们安排每系列调用只查找一次所需的I/O信息，
	 * 假设记录类型在我们下面不会改变。
	 */
	fc_my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
	if (fc_my_extra == NULL ||
		fc_my_extra->ncolumns != fc_ncolumns)
	{
		fcinfo->flinfo->fn_extra =
			MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
							   offsetof(RecordIOData, columns) +
							   fc_ncolumns * sizeof(ColumnIOData));
		fc_my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
		fc_my_extra->record_type = InvalidOid;
		fc_my_extra->record_typmod = 0;
	}

	if (fc_my_extra->record_type != fc_tupType ||
		fc_my_extra->record_typmod != fc_tupTypmod)
	{
		MemSet(fc_my_extra, 0,
			   offsetof(RecordIOData, columns) +
			   fc_ncolumns * sizeof(ColumnIOData));
		fc_my_extra->record_type = fc_tupType;
		fc_my_extra->record_typmod = fc_tupTypmod;
		fc_my_extra->ncolumns = fc_ncolumns;
	}

	fc_values = (Datum *) palloc(fc_ncolumns * sizeof(Datum));
	fc_nulls = (bool *) palloc(fc_ncolumns * sizeof(bool));

	/*
	 * 扫描字符串。我们使用 "buf" 来累积每个列的去引号数据， 
	 * 然后将其传递给适当的输入转换器。
	 */
	fc_ptr = fc_string;
	/* 允许前导空格 */
	while (*fc_ptr && isspace((unsigned char) *fc_ptr))
		fc_ptr++;
	if (*fc_ptr++ != '(')
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
				 errmsg("malformed record literal: \"%s\"", fc_string),
				 errdetail("Missing left parenthesis.")));

	initStringInfo(&fc_buf);

	for (fc_i = 0; fc_i < fc_ncolumns; fc_i++)
	{
		Form_pg_attribute fc_att = TupleDescAttr(fc_tupdesc, fc_i);
		ColumnIOData *fc_column_info = &fc_my_extra->columns[fc_i];
		Oid			fc_column_type = fc_att->atttypid;
		char	   *fc_column_data;

		/* 在数据类型中忽略掉已丢弃的列，但用 NULL 填充 */
		if (fc_att->attisdropped)
		{
			fc_values[fc_i] = (Datum) 0;
			fc_nulls[fc_i] = true;
			continue;
		}

		if (fc_needComma)
		{
			/* 跳过分隔先前字段与当前字段的逗号 */
			if (*fc_ptr == ',')
				fc_ptr++;
			else
				/* *ptr 必须是 ')' */
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
						 errmsg("malformed record literal: \"%s\"", fc_string),
						 errdetail("Too few columns.")));
		}

		/* 检查空值：完全空的输入意味着 null */
		if (*fc_ptr == ',' || *fc_ptr == ')')
		{
			fc_column_data = NULL;
			fc_nulls[fc_i] = true;
		}
		else
		{
			/* 提取该列的字符串 */
			bool		fc_inquote = false;

			resetStringInfo(&fc_buf);
			while (fc_inquote || !(*fc_ptr == ',' || *fc_ptr == ')'))
			{
				char		fc_ch = *fc_ptr++;

				if (fc_ch == '\0')
					ereport(ERROR,
							(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
							 errmsg("malformed record literal: \"%s\"",
									fc_string),
							 errdetail("Unexpected end of input.")));
				if (fc_ch == '\\')
				{
					if (*fc_ptr == '\0')
						ereport(ERROR,
								(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
								 errmsg("malformed record literal: \"%s\"",
										fc_string),
								 errdetail("Unexpected end of input.")));
					appendStringInfoChar(&fc_buf, *fc_ptr++);
				}
				else if (fc_ch == '"')
				{
					if (!fc_inquote)
						fc_inquote = true;
					else if (*fc_ptr == '"')
					{
						/* 引号序列中的双引号 */
						appendStringInfoChar(&fc_buf, *fc_ptr++);
					}
					else
						fc_inquote = false;
				}
				else
					appendStringInfoChar(&fc_buf, fc_ch);
			}

			fc_column_data = fc_buf.data;
			fc_nulls[fc_i] = false;
		}

		/*
		 * 转换列值
		 */
		if (fc_column_info->column_type != fc_column_type)
		{
			getTypeInputInfo(fc_column_type,
							 &fc_column_info->typiofunc,
							 &fc_column_info->typioparam);
			fmgr_info_cxt(fc_column_info->typiofunc, &fc_column_info->proc,
						  fcinfo->flinfo->fn_mcxt);
			fc_column_info->column_type = fc_column_type;
		}

		fc_values[fc_i] = InputFunctionCall(&fc_column_info->proc,
									  fc_column_data,
									  fc_column_info->typioparam,
									  fc_att->atttypmod);

		/*
		 * 为下一个列做准备
		 */
		fc_needComma = true;
	}

	if (*fc_ptr++ != ')')
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
				 errmsg("malformed record literal: \"%s\"", fc_string),
				 errdetail("Too many columns.")));
	/* 允许尾随空格 */
	while (*fc_ptr && isspace((unsigned char) *fc_ptr))
		fc_ptr++;
	if (*fc_ptr)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
				 errmsg("malformed record literal: \"%s\"", fc_string),
				 errdetail("Junk after right parenthesis.")));

	fc_tuple = heap_form_tuple(fc_tupdesc, fc_values, fc_nulls);

	/*
	 * 我们不能返回 tuple->t_data，因为 heap_form_tuple 将其分配为 
	 * 更大块的一部分，我们的调用者可能期望能够释放我们的结果。
	 * 所以必须将信息复制到一个新的 palloc 块中。
	 */
	fc_result = (HeapTupleHeader) palloc(fc_tuple->t_len);
	memcpy(fc_result, fc_tuple->t_data, fc_tuple->t_len);

	heap_freetuple(fc_tuple);
	pfree(fc_buf.data);
	pfree(fc_values);
	pfree(fc_nulls);
	ReleaseTupleDesc(fc_tupdesc);

	PG_RETURN_HEAPTUPLEHEADER(fc_result);
}

/*
 * record_out		- 任何复合类型的输出例程。
 */
Datum record_out(PG_FUNCTION_ARGS)
{
	HeapTupleHeader fc_rec = PG_GETARG_HEAPTUPLEHEADER(0);
	Oid			fc_tupType;
	int32		fc_tupTypmod;
	TupleDesc	fc_tupdesc;
	HeapTupleData fc_tuple;
	RecordIOData *fc_my_extra;
	bool		fc_needComma = false;
	int			fc_ncolumns;
	int			fc_i;
	Datum	   *fc_values;
	bool	   *fc_nulls;
	StringInfoData fc_buf;

	check_stack_depth();		/* 对记录类型的列进行递归 */

	/* 从元组本身提取类型信息 */
	fc_tupType = HeapTupleHeaderGetTypeId(fc_rec);
	fc_tupTypmod = HeapTupleHeaderGetTypMod(fc_rec);
	fc_tupdesc = lookup_rowtype_tupdesc(fc_tupType, fc_tupTypmod);
	fc_ncolumns = fc_tupdesc->natts;

	/* 构建一个临时 HeapTuple 控制结构 */
	fc_tuple.t_len = HeapTupleHeaderGetDatumLength(fc_rec);
	ItemPointerSetInvalid(&(fc_tuple.t_self));
	fc_tuple.t_tableOid = InvalidOid;
	fc_tuple.t_data = fc_rec;

	/*
	 * 我们安排每系列调用只查找一次所需的I/O信息，
	 * 假设记录类型在我们下面不会改变。
	 */
	fc_my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
	if (fc_my_extra == NULL ||
		fc_my_extra->ncolumns != fc_ncolumns)
	{
		fcinfo->flinfo->fn_extra =
			MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
							   offsetof(RecordIOData, columns) +
							   fc_ncolumns * sizeof(ColumnIOData));
		fc_my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
		fc_my_extra->record_type = InvalidOid;
		fc_my_extra->record_typmod = 0;
	}

	if (fc_my_extra->record_type != fc_tupType ||
		fc_my_extra->record_typmod != fc_tupTypmod)
	{
		MemSet(fc_my_extra, 0,
			   offsetof(RecordIOData, columns) +
			   fc_ncolumns * sizeof(ColumnIOData));
		fc_my_extra->record_type = fc_tupType;
		fc_my_extra->record_typmod = fc_tupTypmod;
		fc_my_extra->ncolumns = fc_ncolumns;
	}

	fc_values = (Datum *) palloc(fc_ncolumns * sizeof(Datum));
	fc_nulls = (bool *) palloc(fc_ncolumns * sizeof(bool));

	/* 将元组分解为字段 */
	heap_deform_tuple(&fc_tuple, fc_tupdesc, fc_values, fc_nulls);

	/* 并构建结果字符串 */
	initStringInfo(&fc_buf);

	appendStringInfoChar(&fc_buf, '(');

	for (fc_i = 0; fc_i < fc_ncolumns; fc_i++)
	{
		Form_pg_attribute fc_att = TupleDescAttr(fc_tupdesc, fc_i);
		ColumnIOData *fc_column_info = &fc_my_extra->columns[fc_i];
		Oid			fc_column_type = fc_att->atttypid;
		Datum		fc_attr;
		char	   *fc_value;
		char	   *fc_tmp;
		bool		fc_nq;

		/* 忽略数据类型中被丢弃的列 */
		if (fc_att->attisdropped)
			continue;

		if (fc_needComma)
			appendStringInfoChar(&fc_buf, ',');
		fc_needComma = true;

		if (fc_nulls[fc_i])
		{
			/* 不发出任何内容…… */
			continue;
		}

		/*
		 * 将列值转换为文本
		 */
		if (fc_column_info->column_type != fc_column_type)
		{
			getTypeOutputInfo(fc_column_type,
							  &fc_column_info->typiofunc,
							  &fc_column_info->typisvarlena);
			fmgr_info_cxt(fc_column_info->typiofunc, &fc_column_info->proc,
						  fcinfo->flinfo->fn_mcxt);
			fc_column_info->column_type = fc_column_type;
		}

		fc_attr = fc_values[fc_i];
		fc_value = OutputFunctionCall(&fc_column_info->proc, fc_attr);

		/* 检测是否需要为该值添加双引号 */
		fc_nq = (fc_value[0] == '\0');	/* 强制对空字符串使用引号 */
		for (fc_tmp = fc_value; *fc_tmp; fc_tmp++)
		{
			char		fc_ch = *fc_tmp;

			if (fc_ch == '"' || fc_ch == '\\' ||
				fc_ch == '(' || fc_ch == ')' || fc_ch == ',' ||
				isspace((unsigned char) fc_ch))
			{
				fc_nq = true;
				break;
			}
		}

		/* 并输出字符串 */
		if (fc_nq)
			appendStringInfoCharMacro(&fc_buf, '"');
		for (fc_tmp = fc_value; *fc_tmp; fc_tmp++)
		{
			char		fc_ch = *fc_tmp;

			if (fc_ch == '"' || fc_ch == '\\')
				appendStringInfoCharMacro(&fc_buf, fc_ch);
			appendStringInfoCharMacro(&fc_buf, fc_ch);
		}
		if (fc_nq)
			appendStringInfoCharMacro(&fc_buf, '"');
	}

	appendStringInfoChar(&fc_buf, ')');

	pfree(fc_values);
	pfree(fc_nulls);
	ReleaseTupleDesc(fc_tupdesc);

	PG_RETURN_CSTRING(fc_buf.data);
}

/*
 * record_recv		- 任何复合类型的二进制输入例程。
 */
Datum record_recv(PG_FUNCTION_ARGS)
{
	StringInfo	fc_buf = (StringInfo) PG_GETARG_POINTER(0);
	Oid			fc_tupType = PG_GETARG_OID(1);
	int32		fc_tupTypmod = PG_GETARG_INT32(2);
	HeapTupleHeader fc_result;
	TupleDesc	fc_tupdesc;
	HeapTuple	fc_tuple;
	RecordIOData *fc_my_extra;
	int			fc_ncolumns;
	int			fc_usercols;
	int			fc_validcols;
	int			fc_i;
	Datum	   *fc_values;
	bool	   *fc_nulls;

	check_stack_depth();		/* 对记录类型的列进行递归 */

	/*
	 * 如果我们没有足够的信息来识别目标记录类型，给出友好的错误消息。 
	 * （lookup_rowtype_tupdesc 无论如何会失败，但会有一个不友好的消息。） 
	 * 在普通 SQL 使用中，我们会得到 -1 作为 typmod，因为复合类型和 RECORD 
	 * 在 SQL 层面上没有类型修饰符，因此必须在 RECORD 中失败。 
	 * 然而一些调用者可以提供有效的 typmod，然后我们可以对 RECORD 
	 * 做一些有用的事情。
	 */
	if (fc_tupType == RECORDOID && fc_tupTypmod < 0)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("input of anonymous composite types is not implemented")));

	fc_tupdesc = lookup_rowtype_tupdesc(fc_tupType, fc_tupTypmod);
	fc_ncolumns = fc_tupdesc->natts;

	/*
	 * 我们安排每系列调用只查找一次所需的I/O信息，
	 * 假设记录类型在我们下面不会改变。
	 */
	fc_my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
	if (fc_my_extra == NULL ||
		fc_my_extra->ncolumns != fc_ncolumns)
	{
		fcinfo->flinfo->fn_extra =
			MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
							   offsetof(RecordIOData, columns) +
							   fc_ncolumns * sizeof(ColumnIOData));
		fc_my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
		fc_my_extra->record_type = InvalidOid;
		fc_my_extra->record_typmod = 0;
	}

	if (fc_my_extra->record_type != fc_tupType ||
		fc_my_extra->record_typmod != fc_tupTypmod)
	{
		MemSet(fc_my_extra, 0,
			   offsetof(RecordIOData, columns) +
			   fc_ncolumns * sizeof(ColumnIOData));
		fc_my_extra->record_type = fc_tupType;
		fc_my_extra->record_typmod = fc_tupTypmod;
		fc_my_extra->ncolumns = fc_ncolumns;
	}

	fc_values = (Datum *) palloc(fc_ncolumns * sizeof(Datum));
	fc_nulls = (bool *) palloc(fc_ncolumns * sizeof(bool));

	/* 获取用户认为它拥有的列数 */
	fc_usercols = pq_getmsgint(fc_buf, 4);

	/* 需要扫描以计算未删除的列 */
	fc_validcols = 0;
	for (fc_i = 0; fc_i < fc_ncolumns; fc_i++)
	{
		if (!TupleDescAttr(fc_tupdesc, fc_i)->attisdropped)
			fc_validcols++;
	}
	if (fc_usercols != fc_validcols)
		ereport(ERROR,
				(errcode(ERRCODE_DATATYPE_MISMATCH),
				 errmsg("wrong number of columns: %d, expected %d",
						fc_usercols, fc_validcols)));

	/* 处理每一列 */
	for (fc_i = 0; fc_i < fc_ncolumns; fc_i++)
	{
		Form_pg_attribute fc_att = TupleDescAttr(fc_tupdesc, fc_i);
		ColumnIOData *fc_column_info = &fc_my_extra->columns[fc_i];
		Oid			fc_column_type = fc_att->atttypid;
		Oid			fc_coltypoid;
		int			fc_itemlen;
		StringInfoData fc_item_buf;
		StringInfo	fc_bufptr;
		char		fc_csave;

		/* 在数据类型中忽略掉已丢弃的列，但用 NULL 填充 */
		if (fc_att->attisdropped)
		{
			fc_values[fc_i] = (Datum) 0;
			fc_nulls[fc_i] = true;
			continue;
		}

		/* 检查数据中记录的列类型 */
		fc_coltypoid = pq_getmsgint(fc_buf, sizeof(Oid));

		/*
		 * 从安全的角度来看，输入的列类型是否与我们预期的匹配并不重要： 
		 * 列类型的接收函数必须足够稳健，以应对无效数据。 
		 * 然而，从用户友好的角度来看，抱怨类型不匹配比抛出 
		 * “不正确的二进制格式”错误要好。 
		 * 但有一个问题：只有内置类型的 OID 稳定到可以相信不匹配 
		 * 是一个真实的问题。因此，仅在两个 OID 都在内置范围内时才抱怨。 
		 * 否则，继续使用我们“应该”获得的列类型。
		 */
		if (fc_coltypoid != fc_column_type &&
			fc_coltypoid < FirstGenbkiObjectId &&
			fc_column_type < FirstGenbkiObjectId)
			ereport(ERROR,
					(errcode(ERRCODE_DATATYPE_MISMATCH),
					 errmsg("binary data has type %u (%s) instead of expected %u (%s) in record column %d",
							fc_coltypoid,
							format_type_extended(fc_coltypoid, -1,
												 FORMAT_TYPE_ALLOW_INVALID),
							fc_column_type,
							format_type_extended(fc_column_type, -1,
												 FORMAT_TYPE_ALLOW_INVALID),
							fc_i + 1)));

		/* 获取并检查项长度 */
		fc_itemlen = pq_getmsgint(fc_buf, 4);
		if (fc_itemlen < -1 || fc_itemlen > (fc_buf->len - fc_buf->cursor))
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
					 errmsg("insufficient data left in message")));

		if (fc_itemlen == -1)
		{
			/* -1 长度表示 NULL */
			fc_bufptr = NULL;
			fc_nulls[fc_i] = true;
			fc_csave = 0;			/* 保持编译器安静 */
		}
		else
		{
			/*
			 * 与其四处复制数据，不如设置一个虚假的 
			 * StringInfo 指向输入缓冲区的正确部分。
			 * 我们假设可以在输入缓冲区上乱写，以保持 
			 * StringInfos 以尾随空字符为惯例。
			 */
			fc_item_buf.data = &fc_buf->data[fc_buf->cursor];
			fc_item_buf.maxlen = fc_itemlen + 1;
			fc_item_buf.len = fc_itemlen;
			fc_item_buf.cursor = 0;

			fc_buf->cursor += fc_itemlen;

			fc_csave = fc_buf->data[fc_buf->cursor];
			fc_buf->data[fc_buf->cursor] = '\0';

			fc_bufptr = &fc_item_buf;
			fc_nulls[fc_i] = false;
		}

		/* 现在调用列的接收过程 */
		if (fc_column_info->column_type != fc_column_type)
		{
			getTypeBinaryInputInfo(fc_column_type,
								   &fc_column_info->typiofunc,
								   &fc_column_info->typioparam);
			fmgr_info_cxt(fc_column_info->typiofunc, &fc_column_info->proc,
						  fcinfo->flinfo->fn_mcxt);
			fc_column_info->column_type = fc_column_type;
		}

		fc_values[fc_i] = ReceiveFunctionCall(&fc_column_info->proc,
										fc_bufptr,
										fc_column_info->typioparam,
										fc_att->atttypmod);

		if (fc_bufptr)
		{
			/* 如果没有读取完整个缓冲区则会出问题 */
			if (fc_item_buf.cursor != fc_itemlen)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
						 errmsg("improper binary format in record column %d",
								fc_i + 1)));

			fc_buf->data[fc_buf->cursor] = fc_csave;
		}
	}

	fc_tuple = heap_form_tuple(fc_tupdesc, fc_values, fc_nulls);

	/*
	 * 我们不能返回 tuple->t_data，因为 heap_form_tuple 将其分配为 
	 * 更大块的一部分，我们的调用者可能期望能够释放我们的结果。
	 * 所以必须将信息复制到一个新的 palloc 块中。
	 */
	fc_result = (HeapTupleHeader) palloc(fc_tuple->t_len);
	memcpy(fc_result, fc_tuple->t_data, fc_tuple->t_len);

	heap_freetuple(fc_tuple);
	pfree(fc_values);
	pfree(fc_nulls);
	ReleaseTupleDesc(fc_tupdesc);

	PG_RETURN_HEAPTUPLEHEADER(fc_result);
}

/*
 * record_send		- 任何复合类型的二进制输出例程。
 */
Datum record_send(PG_FUNCTION_ARGS)
{
	HeapTupleHeader fc_rec = PG_GETARG_HEAPTUPLEHEADER(0);
	Oid			fc_tupType;
	int32		fc_tupTypmod;
	TupleDesc	fc_tupdesc;
	HeapTupleData fc_tuple;
	RecordIOData *fc_my_extra;
	int			fc_ncolumns;
	int			fc_validcols;
	int			fc_i;
	Datum	   *fc_values;
	bool	   *fc_nulls;
	StringInfoData fc_buf;

	check_stack_depth();		/* 对记录类型的列进行递归 */

	/* 从元组本身提取类型信息 */
	fc_tupType = HeapTupleHeaderGetTypeId(fc_rec);
	fc_tupTypmod = HeapTupleHeaderGetTypMod(fc_rec);
	fc_tupdesc = lookup_rowtype_tupdesc(fc_tupType, fc_tupTypmod);
	fc_ncolumns = fc_tupdesc->natts;

	/* 构建一个临时 HeapTuple 控制结构 */
	fc_tuple.t_len = HeapTupleHeaderGetDatumLength(fc_rec);
	ItemPointerSetInvalid(&(fc_tuple.t_self));
	fc_tuple.t_tableOid = InvalidOid;
	fc_tuple.t_data = fc_rec;

	/*
	 * 我们安排每系列调用只查找一次所需的I/O信息，
	 * 假设记录类型在我们下面不会改变。
	 */
	fc_my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
	if (fc_my_extra == NULL ||
		fc_my_extra->ncolumns != fc_ncolumns)
	{
		fcinfo->flinfo->fn_extra =
			MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
							   offsetof(RecordIOData, columns) +
							   fc_ncolumns * sizeof(ColumnIOData));
		fc_my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra;
		fc_my_extra->record_type = InvalidOid;
		fc_my_extra->record_typmod = 0;
	}

	if (fc_my_extra->record_type != fc_tupType ||
		fc_my_extra->record_typmod != fc_tupTypmod)
	{
		MemSet(fc_my_extra, 0,
			   offsetof(RecordIOData, columns) +
			   fc_ncolumns * sizeof(ColumnIOData));
		fc_my_extra->record_type = fc_tupType;
		fc_my_extra->record_typmod = fc_tupTypmod;
		fc_my_extra->ncolumns = fc_ncolumns;
	}

	fc_values = (Datum *) palloc(fc_ncolumns * sizeof(Datum));
	fc_nulls = (bool *) palloc(fc_ncolumns * sizeof(bool));

	/* 将元组分解为字段 */
	heap_deform_tuple(&fc_tuple, fc_tupdesc, fc_values, fc_nulls);

	/* 并构建结果字符串 */
	pq_begintypsend(&fc_buf);

	/* 需要扫描以计算未删除的列 */
	fc_validcols = 0;
	for (fc_i = 0; fc_i < fc_ncolumns; fc_i++)
	{
		if (!TupleDescAttr(fc_tupdesc, fc_i)->attisdropped)
			fc_validcols++;
	}
	pq_sendint32(&fc_buf, fc_validcols);

	for (fc_i = 0; fc_i < fc_ncolumns; fc_i++)
	{
		Form_pg_attribute fc_att = TupleDescAttr(fc_tupdesc, fc_i);
		ColumnIOData *fc_column_info = &fc_my_extra->columns[fc_i];
		Oid			fc_column_type = fc_att->atttypid;
		Datum		fc_attr;
		bytea	   *fc_outputbytes;

		/* 忽略数据类型中被丢弃的列 */
		if (fc_att->attisdropped)
			continue;

		pq_sendint32(&fc_buf, fc_column_type);

		if (fc_nulls[fc_i])
		{
			/* 发送 -1 数据长度以表示 NULL */
			pq_sendint32(&fc_buf, -1);
			continue;
		}

		
/*
		 * 将列值转换为二进制
		 */
		if (fc_column_info->column_type != fc_column_type)
		{
			getTypeBinaryOutputInfo(fc_column_type,
									&fc_column_info->typiofunc,
									&fc_column_info->typisvarlena);
			fmgr_info_cxt(fc_column_info->typiofunc, &fc_column_info->proc,
						  fcinfo->flinfo->fn_mcxt);
			fc_column_info->column_type = fc_column_type;
		}

		fc_attr = fc_values[fc_i];
		fc_outputbytes = SendFunctionCall(&fc_column_info->proc, fc_attr);
		pq_sendint32(&fc_buf, VARSIZE(fc_outputbytes) - VARHDRSZ);
		pq_sendbytes(&fc_buf, VARDATA(fc_outputbytes),
					 VARSIZE(fc_outputbytes) - VARHDRSZ);
	}

	pfree(fc_values);
	pfree(fc_nulls);
	ReleaseTupleDesc(fc_tupdesc);

	PG_RETURN_BYTEA_P(pq_endtypsend(&fc_buf));
}


/*
 * record_cmp()
 * 用于记录的内部比较函数。
 *
 * 返回 -1，0 或 1
 *
 * 不要假设两个输入是完全相同的记录类型；
 * 例如，我们可能在比较一个匿名的 ROW() 结构与一个
 * 命名的复合类型。只要它们具有相同数量
 * 的非丢弃列且类型相同，我们将进行比较。
 */
static int fc_record_cmp(FunctionCallInfo fcinfo)
{
	HeapTupleHeader fc_record1 = PG_GETARG_HEAPTUPLEHEADER(0);
	HeapTupleHeader fc_record2 = PG_GETARG_HEAPTUPLEHEADER(1);
	int			fc_result = 0;
	Oid			fc_tupType1;
	Oid			fc_tupType2;
	int32		fc_tupTypmod1;
	int32		fc_tupTypmod2;
	TupleDesc	fc_tupdesc1;
	TupleDesc	fc_tupdesc2;
	HeapTupleData fc_tuple1;
	HeapTupleData fc_tuple2;
	int			fc_ncolumns1;
	int			fc_ncolumns2;
	RecordCompareData *fc_my_extra;
	int			fc_ncols;
	Datum	   *fc_values1;
	Datum	   *fc_values2;
	bool	   *fc_nulls1;
	bool	   *fc_nulls2;
	int			fc_i1;
	int			fc_i2;
	int			fc_j;

	check_stack_depth();		/* 对记录类型的列进行递归 */

	/* 从元组中提取类型信息 */
	fc_tupType1 = HeapTupleHeaderGetTypeId(fc_record1);
	fc_tupTypmod1 = HeapTupleHeaderGetTypMod(fc_record1);
	fc_tupdesc1 = lookup_rowtype_tupdesc(fc_tupType1, fc_tupTypmod1);
	fc_ncolumns1 = fc_tupdesc1->natts;
	fc_tupType2 = HeapTupleHeaderGetTypeId(fc_record2);
	fc_tupTypmod2 = HeapTupleHeaderGetTypMod(fc_record2);
	fc_tupdesc2 = lookup_rowtype_tupdesc(fc_tupType2, fc_tupTypmod2);
	fc_ncolumns2 = fc_tupdesc2->natts;

	/* 构建临时 HeapTuple 控制结构 */
	fc_tuple1.t_len = HeapTupleHeaderGetDatumLength(fc_record1);
	ItemPointerSetInvalid(&(fc_tuple1.t_self));
	fc_tuple1.t_tableOid = InvalidOid;
	fc_tuple1.t_data = fc_record1;
	fc_tuple2.t_len = HeapTupleHeaderGetDatumLength(fc_record2);
	ItemPointerSetInvalid(&(fc_tuple2.t_self));
	fc_tuple2.t_tableOid = InvalidOid;
	fc_tuple2.t_data = fc_record2;

	/*
	 * 我们安排仅在每一系列调用中查找所需的比较信息一次，
	 * 假设记录类型不会在我们之下发生变化。
	 */
	fc_ncols = Max(fc_ncolumns1, fc_ncolumns2);
	fc_my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
	if (fc_my_extra == NULL ||
		fc_my_extra->ncolumns < fc_ncols)
	{
		fcinfo->flinfo->fn_extra =
			MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
							   offsetof(RecordCompareData, columns) +
							   fc_ncols * sizeof(ColumnCompareData));
		fc_my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
		fc_my_extra->ncolumns = fc_ncols;
		fc_my_extra->record1_type = InvalidOid;
		fc_my_extra->record1_typmod = 0;
		fc_my_extra->record2_type = InvalidOid;
		fc_my_extra->record2_typmod = 0;
	}

	if (fc_my_extra->record1_type != fc_tupType1 ||
		fc_my_extra->record1_typmod != fc_tupTypmod1 ||
		fc_my_extra->record2_type != fc_tupType2 ||
		fc_my_extra->record2_typmod != fc_tupTypmod2)
	{
		MemSet(fc_my_extra->columns, 0, fc_ncols * sizeof(ColumnCompareData));
		fc_my_extra->record1_type = fc_tupType1;
		fc_my_extra->record1_typmod = fc_tupTypmod1;
		fc_my_extra->record2_type = fc_tupType2;
		fc_my_extra->record2_typmod = fc_tupTypmod2;
	}

	/* 将元组分解为字段 */
	fc_values1 = (Datum *) palloc(fc_ncolumns1 * sizeof(Datum));
	fc_nulls1 = (bool *) palloc(fc_ncolumns1 * sizeof(bool));
	heap_deform_tuple(&fc_tuple1, fc_tupdesc1, fc_values1, fc_nulls1);
	fc_values2 = (Datum *) palloc(fc_ncolumns2 * sizeof(Datum));
	fc_nulls2 = (bool *) palloc(fc_ncolumns2 * sizeof(bool));
	heap_deform_tuple(&fc_tuple2, fc_tupdesc2, fc_values2, fc_nulls2);

	/*
	 * 扫描对应的列，允许在两个行的不同位置有丢弃列。
	 * i1 和 i2 是物理列索引，j 是
	 * 逻辑列索引。
	 */
	fc_i1 = fc_i2 = fc_j = 0;
	while (fc_i1 < fc_ncolumns1 || fc_i2 < fc_ncolumns2)
	{
		Form_pg_attribute fc_att1;
		Form_pg_attribute fc_att2;
		TypeCacheEntry *fc_typentry;
		Oid			fc_collation;

		/*
		 * 跳过丢弃的列
		 */
		if (fc_i1 < fc_ncolumns1 && TupleDescAttr(fc_tupdesc1, fc_i1)->attisdropped)
		{
			fc_i1++;
			continue;
		}
		if (fc_i2 < fc_ncolumns2 && TupleDescAttr(fc_tupdesc2, fc_i2)->attisdropped)
		{
			fc_i2++;
			continue;
		}
		if (fc_i1 >= fc_ncolumns1 || fc_i2 >= fc_ncolumns2)
			break;				/* 我们将在下面的循环中处理不匹配 */

		fc_att1 = TupleDescAttr(fc_tupdesc1, fc_i1);
		fc_att2 = TupleDescAttr(fc_tupdesc2, fc_i2);

		/*
		 * 有两个匹配的列，它们必须是相同类型
		 */
		if (fc_att1->atttypid != fc_att2->atttypid)
			ereport(ERROR,
					(errcode(ERRCODE_DATATYPE_MISMATCH),
					 errmsg("cannot compare dissimilar column types %s and %s at record column %d",
							format_type_be(fc_att1->atttypid),
							format_type_be(fc_att2->atttypid),
							fc_j + 1)));

		/*
		 * 如果它们的排序规则不同，我们在这里不抱怨，但
		 * 比较函数可能会。
		 */
		fc_collation = fc_att1->attcollation;
		if (fc_collation != fc_att2->attcollation)
			fc_collation = InvalidOid;

		/*
		 * 如果尚未完成，查找比较函数
		 */
		fc_typentry = fc_my_extra->columns[fc_j].typentry;
		if (fc_typentry == NULL ||
			fc_typentry->type_id != fc_att1->atttypid)
		{
			fc_typentry = lookup_type_cache(fc_att1->atttypid,
										 TYPECACHE_CMP_PROC_FINFO);
			if (!OidIsValid(fc_typentry->cmp_proc_finfo.fn_oid))
				ereport(ERROR,
						(errcode(ERRCODE_UNDEFINED_FUNCTION),
						 errmsg("could not identify a comparison function for type %s",
								format_type_be(fc_typentry->type_id))));
			fc_my_extra->columns[fc_j].typentry = fc_typentry;
		}

		/*
		 * 我们认为两个 NULL 相等；NULL > 非 NULL。
		 */
		if (!fc_nulls1[fc_i1] || !fc_nulls2[fc_i2])
		{
			LOCAL_FCINFO(fc_locfcinfo, 2);
			int32		fc_cmpresult;

			if (fc_nulls1[fc_i1])
			{
				/* arg1 大于 arg2 */
				fc_result = 1;
				break;
			}
			if (fc_nulls2[fc_i2])
			{
				/* arg1 小于 arg2 */
				fc_result = -1;
				break;
			}

			/* 比较元素对 */
			InitFunctionCallInfoData(*fc_locfcinfo, &fc_typentry->cmp_proc_finfo, 2,
									 fc_collation, NULL, NULL);
			fc_locfcinfo->args[0].value = fc_values1[fc_i1];
			fc_locfcinfo->args[0].isnull = false;
			fc_locfcinfo->args[1].value = fc_values2[fc_i2];
			fc_locfcinfo->args[1].isnull = false;
			fc_cmpresult = DatumGetInt32(FunctionCallInvoke(fc_locfcinfo));

			/* 我们不期望比较支持函数返回 NULL */
			Assert(!fc_locfcinfo->isnull);

			if (fc_cmpresult < 0)
			{
				/* arg1 小于 arg2 */
				fc_result = -1;
				break;
			}
			else if (fc_cmpresult > 0)
			{
				/* arg1 大于 arg2 */
				fc_result = 1;
				break;
			}
		}

		/* 相等，因此继续到下一个列 */
		fc_i1++, fc_i2++, fc_j++;
	}

	/*
	 * 如果我们没有提前退出循环，请检查列计数
	 * 不匹配。 （如果我们发现列值不相等，我们不会报告这种不匹配；这是一种特性还是一个错误？）
	 */
	if (fc_result == 0)
	{
		if (fc_i1 != fc_ncolumns1 || fc_i2 != fc_ncolumns2)
			ereport(ERROR,
					(errcode(ERRCODE_DATATYPE_MISMATCH),
					 errmsg("cannot compare record types with different numbers of columns")));
	}

	pfree(fc_values1);
	pfree(fc_nulls1);
	pfree(fc_values2);
	pfree(fc_nulls2);
	ReleaseTupleDesc(fc_tupdesc1);
	ReleaseTupleDesc(fc_tupdesc2);

	/* 避免在处理已烤制输入时泄漏内存。 */
	PG_FREE_IF_COPY(fc_record1, 0);
	PG_FREE_IF_COPY(fc_record2, 1);

	return fc_result;
}

/*
 * record_eq :
 *		  比较两个记录的相等性
 * 结果：
 *		  如果记录相等，则返回真，否则返回假。
 *
 * 注意：我们在这里不使用 record_cmp，因为在
 * 没有总排序（因此没有 btree 支持）的数据类型中，相等性可能是有意义的。
 */
Datum record_eq(PG_FUNCTION_ARGS)
{
	HeapTupleHeader fc_record1 = PG_GETARG_HEAPTUPLEHEADER(0);
	HeapTupleHeader fc_record2 = PG_GETARG_HEAPTUPLEHEADER(1);
	bool		fc_result = true;
	Oid			fc_tupType1;
	Oid			fc_tupType2;
	int32		fc_tupTypmod1;
	int32		fc_tupTypmod2;
	TupleDesc	fc_tupdesc1;
	TupleDesc	fc_tupdesc2;
	HeapTupleData fc_tuple1;
	HeapTupleData fc_tuple2;
	int			fc_ncolumns1;
	int			fc_ncolumns2;
	RecordCompareData *fc_my_extra;
	int			fc_ncols;
	Datum	   *fc_values1;
	Datum	   *fc_values2;
	bool	   *fc_nulls1;
	bool	   *fc_nulls2;
	int			fc_i1;
	int			fc_i2;
	int			fc_j;

	check_stack_depth();		/* 对记录类型的列进行递归 */

	/* 从元组中提取类型信息 */
	fc_tupType1 = HeapTupleHeaderGetTypeId(fc_record1);
	fc_tupTypmod1 = HeapTupleHeaderGetTypMod(fc_record1);
	fc_tupdesc1 = lookup_rowtype_tupdesc(fc_tupType1, fc_tupTypmod1);
	fc_ncolumns1 = fc_tupdesc1->natts;
	fc_tupType2 = HeapTupleHeaderGetTypeId(fc_record2);
	fc_tupTypmod2 = HeapTupleHeaderGetTypMod(fc_record2);
	fc_tupdesc2 = lookup_rowtype_tupdesc(fc_tupType2, fc_tupTypmod2);
	fc_ncolumns2 = fc_tupdesc2->natts;

	/* 构建临时 HeapTuple 控制结构 */
	fc_tuple1.t_len = HeapTupleHeaderGetDatumLength(fc_record1);
	ItemPointerSetInvalid(&(fc_tuple1.t_self));
	fc_tuple1.t_tableOid = InvalidOid;
	fc_tuple1.t_data = fc_record1;
	fc_tuple2.t_len = HeapTupleHeaderGetDatumLength(fc_record2);
	ItemPointerSetInvalid(&(fc_tuple2.t_self));
	fc_tuple2.t_tableOid = InvalidOid;
	fc_tuple2.t_data = fc_record2;

	/*
	 * 我们安排仅在每一系列调用中查找所需的比较信息一次，
	 * 假设记录类型不会在我们之下发生变化。
	 */
	fc_ncols = Max(fc_ncolumns1, fc_ncolumns2);
	fc_my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
	if (fc_my_extra == NULL ||
		fc_my_extra->ncolumns < fc_ncols)
	{
		fcinfo->flinfo->fn_extra =
			MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
							   offsetof(RecordCompareData, columns) +
							   fc_ncols * sizeof(ColumnCompareData));
		fc_my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
		fc_my_extra->ncolumns = fc_ncols;
		fc_my_extra->record1_type = InvalidOid;
		fc_my_extra->record1_typmod = 0;
		fc_my_extra->record2_type = InvalidOid;
		fc_my_extra->record2_typmod = 0;
	}

	if (fc_my_extra->record1_type != fc_tupType1 ||
		fc_my_extra->record1_typmod != fc_tupTypmod1 ||
		fc_my_extra->record2_type != fc_tupType2 ||
		fc_my_extra->record2_typmod != fc_tupTypmod2)
	{
		MemSet(fc_my_extra->columns, 0, fc_ncols * sizeof(ColumnCompareData));
		fc_my_extra->record1_type = fc_tupType1;
		fc_my_extra->record1_typmod = fc_tupTypmod1;
		fc_my_extra->record2_type = fc_tupType2;
		fc_my_extra->record2_typmod = fc_tupTypmod2;
	}

	/* 将元组分解为字段 */
	fc_values1 = (Datum *) palloc(fc_ncolumns1 * sizeof(Datum));
	fc_nulls1 = (bool *) palloc(fc_ncolumns1 * sizeof(bool));
	heap_deform_tuple(&fc_tuple1, fc_tupdesc1, fc_values1, fc_nulls1);
	fc_values2 = (Datum *) palloc(fc_ncolumns2 * sizeof(Datum));
	fc_nulls2 = (bool *) palloc(fc_ncolumns2 * sizeof(bool));
	heap_deform_tuple(&fc_tuple2, fc_tupdesc2, fc_values2, fc_nulls2);

	/*
	 * 扫描对应的列，允许在两个行的不同位置有丢弃列。
	 * i1 和 i2 是物理列索引，j 是
	 * 逻辑列索引。
	 */
	fc_i1 = fc_i2 = fc_j = 0;
	while (fc_i1 < fc_ncolumns1 || fc_i2 < fc_ncolumns2)
	{
		LOCAL_FCINFO(fc_locfcinfo, 2);
		Form_pg_attribute fc_att1;
		Form_pg_attribute fc_att2;
		TypeCacheEntry *fc_typentry;
		Oid			fc_collation;
		bool		fc_oprresult;

		/*
		 * 跳过丢弃的列
		 */
		if (fc_i1 < fc_ncolumns1 && TupleDescAttr(fc_tupdesc1, fc_i1)->attisdropped)
		{
			fc_i1++;
			continue;
		}
		if (fc_i2 < fc_ncolumns2 && TupleDescAttr(fc_tupdesc2, fc_i2)->attisdropped)
		{
			fc_i2++;
			continue;
		}
		if (fc_i1 >= fc_ncolumns1 || fc_i2 >= fc_ncolumns2)
			break;				/* 我们将在下面的循环中处理不匹配 */

		fc_att1 = TupleDescAttr(fc_tupdesc1, fc_i1);
		fc_att2 = TupleDescAttr(fc_tupdesc2, fc_i2);

		/*
		 * 有两个匹配的列，它们必须是相同类型
		 */
		if (fc_att1->atttypid != fc_att2->atttypid)
			ereport(ERROR,
					(errcode(ERRCODE_DATATYPE_MISMATCH),
					 errmsg("cannot compare dissimilar column types %s and %s at record column %d",
							format_type_be(fc_att1->atttypid),
							format_type_be(fc_att2->atttypid),
							fc_j + 1)));

		/*
		 * 如果它们的排序规则不同，我们在这里不抱怨，但
		 * 相等函数可能会。
		 */
		fc_collation = fc_att1->attcollation;
		if (fc_collation != fc_att2->attcollation)
			fc_collation = InvalidOid;

		/*
		 * 如果尚未完成，查找相等函数
		 */
		fc_typentry = fc_my_extra->columns[fc_j].typentry;
		if (fc_typentry == NULL ||
			fc_typentry->type_id != fc_att1->atttypid)
		{
			fc_typentry = lookup_type_cache(fc_att1->atttypid,
										 TYPECACHE_EQ_OPR_FINFO);
			if (!OidIsValid(fc_typentry->eq_opr_finfo.fn_oid))
				ereport(ERROR,
						(errcode(ERRCODE_UNDEFINED_FUNCTION),
						 errmsg("could not identify an equality operator for type %s",
								format_type_be(fc_typentry->type_id))));
			fc_my_extra->columns[fc_j].typentry = fc_typentry;
		}

		/*
		 * 我们认为两个 NULL 相等；NULL > 非 NULL。
		 */
		if (!fc_nulls1[fc_i1] || !fc_nulls2[fc_i2])
		{
			if (fc_nulls1[fc_i1] || fc_nulls2[fc_i2])
			{
				fc_result = false;
				break;
			}

			/* 比较元素对 */
			InitFunctionCallInfoData(*fc_locfcinfo, &fc_typentry->eq_opr_finfo, 2,
									 fc_collation, NULL, NULL);
			fc_locfcinfo->args[0].value = fc_values1[fc_i1];
			fc_locfcinfo->args[0].isnull = false;
			fc_locfcinfo->args[1].value = fc_values2[fc_i2];
			fc_locfcinfo->args[1].isnull = false;
			fc_oprresult = DatumGetBool(FunctionCallInvoke(fc_locfcinfo));
			if (fc_locfcinfo->isnull || !fc_oprresult)
			{
				fc_result = false;
				break;
			}
		}

		/* 相等，因此继续到下一个列 */
		fc_i1++, fc_i2++, fc_j++;
	}

	/*
	 * 如果我们没有提前退出循环，请检查列计数
	 * 不匹配。 （如果我们发现列值不相等，我们不会报告这种不匹配；这是一种特性还是一个错误？）
	 */
	if (fc_result)
	{
		if (fc_i1 != fc_ncolumns1 || fc_i2 != fc_ncolumns2)
			ereport(ERROR,
					(errcode(ERRCODE_DATATYPE_MISMATCH),
					 errmsg("cannot compare record types with different numbers of columns")));
	}

	pfree(fc_values1);
	pfree(fc_nulls1);
	pfree(fc_values2);
	pfree(fc_nulls2);
	ReleaseTupleDesc(fc_tupdesc1);
	ReleaseTupleDesc(fc_tupdesc2);

	/* 避免在处理已烤制输入时泄漏内存。 */
	PG_FREE_IF_COPY(fc_record1, 0);
	PG_FREE_IF_COPY(fc_record2, 1);

	PG_RETURN_BOOL(fc_result);
}

Datum record_ne(PG_FUNCTION_ARGS)
{
	PG_RETURN_BOOL(!DatumGetBool(record_eq(fcinfo)));
}

Datum record_lt(PG_FUNCTION_ARGS)
{
	PG_RETURN_BOOL(fc_record_cmp(fcinfo) < 0);
}

Datum record_gt(PG_FUNCTION_ARGS)
{
	PG_RETURN_BOOL(fc_record_cmp(fcinfo) > 0);
}

Datum record_le(PG_FUNCTION_ARGS)
{
	PG_RETURN_BOOL(fc_record_cmp(fcinfo) <= 0);
}

Datum record_ge(PG_FUNCTION_ARGS)
{
	PG_RETURN_BOOL(fc_record_cmp(fcinfo) >= 0);
}

Datum btrecordcmp(PG_FUNCTION_ARGS)
{
	PG_RETURN_INT32(fc_record_cmp(fcinfo));
}


/*
 * record_image_cmp :
 * 用于记录的内部字节导向比较函数。
 *
 * 返回 -1，0 或 1
 *
 * 注意：正常的“相等”概念在这里不适用；被视为相等的值的不同
 * 表示不被视为相同。例如，对于 citext 类型，'A' 和 'a' 是相等的，但
 * 它们并不相同。
 */
static int fc_record_image_cmp(FunctionCallInfo fcinfo)
{
	HeapTupleHeader fc_record1 = PG_GETARG_HEAPTUPLEHEADER(0);
	HeapTupleHeader fc_record2 = PG_GETARG_HEAPTUPLEHEADER(1);
	int			fc_result = 0;
	Oid			fc_tupType1;
	Oid			fc_tupType2;
	int32		fc_tupTypmod1;
	int32		fc_tupTypmod2;
	TupleDesc	fc_tupdesc1;
	TupleDesc	fc_tupdesc2;
	HeapTupleData fc_tuple1;
	HeapTupleData fc_tuple2;
	int			fc_ncolumns1;
	int			fc_ncolumns2;
	RecordCompareData *fc_my_extra;
	int			fc_ncols;
	Datum	   *fc_values1;
	Datum	   *fc_values2;
	bool	   *fc_nulls1;
	bool	   *fc_nulls2;
	int			fc_i1;
	int			fc_i2;
	int			fc_j;

	/* 从元组中提取类型信息 */
	fc_tupType1 = HeapTupleHeaderGetTypeId(fc_record1);
	fc_tupTypmod1 = HeapTupleHeaderGetTypMod(fc_record1);
	fc_tupdesc1 = lookup_rowtype_tupdesc(fc_tupType1, fc_tupTypmod1);
	fc_ncolumns1 = fc_tupdesc1->natts;
	fc_tupType2 = HeapTupleHeaderGetTypeId(fc_record2);
	fc_tupTypmod2 = HeapTupleHeaderGetTypMod(fc_record2);
	fc_tupdesc2 = lookup_rowtype_tupdesc(fc_tupType2, fc_tupTypmod2);
	fc_ncolumns2 = fc_tupdesc2->natts;

	/* 构建临时 HeapTuple 控制结构 */
	fc_tuple1.t_len = HeapTupleHeaderGetDatumLength(fc_record1);
	ItemPointerSetInvalid(&(fc_tuple1.t_self));
	fc_tuple1.t_tableOid = InvalidOid;
	fc_tuple1.t_data = fc_record1;
	fc_tuple2.t_len = HeapTupleHeaderGetDatumLength(fc_record2);
	ItemPointerSetInvalid(&(fc_tuple2.t_self));
	fc_tuple2.t_tableOid = InvalidOid;
	fc_tuple2.t_data = fc_record2;

	/*
	 * 我们安排仅在每一系列调用中查找所需的比较信息一次，
	 * 假设记录类型不会在我们之下发生变化。
	 */
	fc_ncols = Max(fc_ncolumns1, fc_ncolumns2);
	fc_my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
	if (fc_my_extra == NULL ||
		fc_my_extra->ncolumns < fc_ncols)
	{
		fcinfo->flinfo->fn_extra =
			MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
							   offsetof(RecordCompareData, columns) +
							   fc_ncols * sizeof(ColumnCompareData));
		fc_my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
		fc_my_extra->ncolumns = fc_ncols;
		fc_my_extra->record1_type = InvalidOid;
		fc_my_extra->record1_typmod = 0;
		fc_my_extra->record2_type = InvalidOid;
		fc_my_extra->record2_typmod = 0;
	}

	if (fc_my_extra->record1_type != fc_tupType1 ||
		fc_my_extra->record1_typmod != fc_tupTypmod1 ||
		fc_my_extra->record2_type != fc_tupType2 ||
		fc_my_extra->record2_typmod != fc_tupTypmod2)
	{
		MemSet(fc_my_extra->columns, 0, fc_ncols * sizeof(ColumnCompareData));
		fc_my_extra->record1_type = fc_tupType1;
		fc_my_extra->record1_typmod = fc_tupTypmod1;
		fc_my_extra->record2_type = fc_tupType2;
		fc_my_extra->record2_typmod = fc_tupTypmod2;
	}

	/* 将元组分解为字段 */
	fc_values1 = (Datum *) palloc(fc_ncolumns1 * sizeof(Datum));
	fc_nulls1 = (bool *) palloc(fc_ncolumns1 * sizeof(bool));
	heap_deform_tuple(&fc_tuple1, fc_tupdesc1, fc_values1, fc_nulls1);
	fc_values2 = (Datum *) palloc(fc_ncolumns2 * sizeof(Datum));
	fc_nulls2 = (bool *) palloc(fc_ncolumns2 * sizeof(bool));
	heap_deform_tuple(&fc_tuple2, fc_tupdesc2, fc_values2, fc_nulls2);

	/*
	 * 扫描对应的列，允许在两个行的不同位置有丢弃列。
	 * i1 和 i2 是物理列索引，j 是
	 * 逻辑列索引。
	 */
	fc_i1 = fc_i2 = fc_j = 0;
	while (fc_i1 < fc_ncolumns1 || fc_i2 < fc_ncolumns2)
	{
		Form_pg_attribute fc_att1;
		Form_pg_attribute fc_att2;

		/*
		 * 跳过丢弃的列
		 */
		if (fc_i1 < fc_ncolumns1 && TupleDescAttr(fc_tupdesc1, fc_i1)->attisdropped)
		{
			fc_i1++;
			continue;
		}
		if (fc_i2 < fc_ncolumns2 && TupleDescAttr(fc_tupdesc2, fc_i2)->attisdropped)
		{
			fc_i2++;
			continue;
		}
		if (fc_i1 >= fc_ncolumns1 || fc_i2 >= fc_ncolumns2)
			break;				/* 我们将在下面的循环中处理不匹配 */

		fc_att1 = TupleDescAttr(fc_tupdesc1, fc_i1);
		fc_att2 = TupleDescAttr(fc_tupdesc2, fc_i2);

		/*
		 * 有两个匹配的列，它们必须是相同类型
		 */
		if (fc_att1->atttypid != fc_att2->atttypid)
			ereport(ERROR,
					(errcode(ERRCODE_DATATYPE_MISMATCH),
					 errmsg("cannot compare dissimilar column types %s and %s at record column %d",
							format_type_be(fc_att1->atttypid),
							format_type_be(fc_att2->atttypid),
							fc_j + 1)));

		/*
		 * 相同类型应具有相同长度（或两者应为
		 * 可变）。
		 */
		Assert(fc_att1->attlen == fc_att2->attlen);

		/*
		 * 我们认为两个 NULL 相等；NULL > 非 NULL。
		 */
		if (!fc_nulls1[fc_i1] || !fc_nulls2[fc_i2])
		{
			int			fc_cmpresult = 0;

			if (fc_nulls1[fc_i1])
			{
				/* arg1 大于 arg2 */
				fc_result = 1;
				break;
			}
			if (fc_nulls2[fc_i2])
			{
				/* arg1 小于 arg2 */
				fc_result = -1;
				break;
			}

			/* 比较元素对 */
			if (fc_att1->attbyval)
			{
				if (fc_values1[fc_i1] != fc_values2[fc_i2])
					fc_cmpresult = (fc_values1[fc_i1] < fc_values2[fc_i2]) ? -1 : 1;
			}
			else if (fc_att1->attlen > 0)
			{
				fc_cmpresult = memcmp(DatumGetPointer(fc_values1[fc_i1]),
								   DatumGetPointer(fc_values2[fc_i2]),
								   fc_att1->attlen);
			}
			else if (fc_att1->attlen == -1)
			{
				Size		fc_len1,
							fc_len2;
				struct varlena *fc_arg1val;
				struct varlena *fc_arg2val;

				fc_len1 = toast_raw_datum_size(fc_values1[fc_i1]);
				fc_len2 = toast_raw_datum_size(fc_values2[fc_i2]);
				fc_arg1val = PG_DETOAST_DATUM_PACKED(fc_values1[fc_i1]);
				fc_arg2val = PG_DETOAST_DATUM_PACKED(fc_values2[fc_i2]);

				fc_cmpresult = memcmp(VARDATA_ANY(fc_arg1val),
								   VARDATA_ANY(fc_arg2val),
								   Min(fc_len1, fc_len2) - VARHDRSZ);
				if ((fc_cmpresult == 0) && (fc_len1 != fc_len2))
					fc_cmpresult = (fc_len1 < fc_len2) ? -1 : 1;

				if ((Pointer) fc_arg1val != (Pointer) fc_values1[fc_i1])
					pfree(fc_arg1val);
				if ((Pointer) fc_arg2val != (Pointer) fc_values2[fc_i2])
					pfree(fc_arg2val);
			}
			else
				elog(ERROR, "unexpected attlen: %d", fc_att1->attlen);

			if (fc_cmpresult < 0)
			{
				/* arg1 小于 arg2 */
				fc_result = -1;
				break;
			}
			else if (fc_cmpresult > 0)
			{
				/* arg1 大于 arg2 */
				fc_result = 1;
				break;
			}
		}

		/* 相等，因此继续到下一个列 */
		fc_i1++, fc_i2++, fc_j++;
	}

	/*
	 * 如果我们没有提前退出循环，请检查列计数
	 * 不匹配。 （如果我们发现列值不相等，我们不会报告这种不匹配；这是一种特性还是一个错误？）
	 */
	if (fc_result == 0)
	{
		if (fc_i1 != fc_ncolumns1 || fc_i2 != fc_ncolumns2)
			ereport(ERROR,
					(errcode(ERRCODE_DATATYPE_MISMATCH),
					 errmsg("cannot compare record types with different numbers of columns")));
	}

	pfree(fc_values1);
	pfree(fc_nulls1);
	pfree(fc_values2);
	pfree(fc_nulls2);
	ReleaseTupleDesc(fc_tupdesc1);
	ReleaseTupleDesc(fc_tupdesc2);

	/* 避免在处理已烤制输入时泄漏内存。 */
	PG_FREE_IF_COPY(fc_record1, 0);
	PG_FREE_IF_COPY(fc_record2, 1);

	return fc_result;
}

/*
 * record_image_eq :
 *		  基于字节图像比较两个记录的相同内容
 * 结果：
 *		  如果记录是相同的，则返回真，否则返回假。
 *
 * 注意：我们在这里不使用 record_image_cmp，因为我们可以避免
 * 对于不同长度的去烤制。
 */
Datum record_image_eq(PG_FUNCTION_ARGS)
{
	HeapTupleHeader fc_record1 = PG_GETARG_HEAPTUPLEHEADER(0);
	HeapTupleHeader fc_record2 = PG_GETARG_HEAPTUPLEHEADER(1);
	bool		fc_result = true;
	Oid			fc_tupType1;
	Oid			fc_tupType2;
	int32		fc_tupTypmod1;
	int32		fc_tupTypmod2;
	TupleDesc	fc_tupdesc1;
	TupleDesc	fc_tupdesc2;
	HeapTupleData fc_tuple1;
	HeapTupleData fc_tuple2;
	int			fc_ncolumns1;
	int			fc_ncolumns2;
	RecordCompareData *fc_my_extra;
	int			fc_ncols;
	Datum	   *fc_values1;
	Datum	   *fc_values2;
	bool	   *fc_nulls1;
	bool	   *fc_nulls2;
	int			fc_i1;
	int			fc_i2;
	int			fc_j;

	/* 从元组中提取类型信息 */
	fc_tupType1 = HeapTupleHeaderGetTypeId(fc_record1);
	fc_tupTypmod1 = HeapTupleHeaderGetTypMod(fc_record1);
	fc_tupdesc1 = lookup_rowtype_tupdesc(fc_tupType1, fc_tupTypmod1);
	fc_ncolumns1 = fc_tupdesc1->natts;
	fc_tupType2 = HeapTupleHeaderGetTypeId(fc_record2);
	fc_tupTypmod2 = HeapTupleHeaderGetTypMod(fc_record2);
	fc_tupdesc2 = lookup_rowtype_tupdesc(fc_tupType2, fc_tupTypmod2);
	fc_ncolumns2 = fc_tupdesc2->natts;

	/* 构建临时 HeapTuple 控制结构 */
	fc_tuple1.t_len = HeapTupleHeaderGetDatumLength(fc_record1);
	ItemPointerSetInvalid(&(fc_tuple1.t_self));
	fc_tuple1.t_tableOid = InvalidOid;
	fc_tuple1.t_data = fc_record1;
	fc_tuple2.t_len = HeapTupleHeaderGetDatumLength(fc_record2);
	ItemPointerSetInvalid(&(fc_tuple2.t_self));
	fc_tuple2.t_tableOid = InvalidOid;
	fc_tuple2.t_data = fc_record2;

	/*
	 * 我们安排仅在每一系列调用中查找所需的比较信息一次，
	 * 假设记录类型不会在我们之下发生变化。
	 */
	fc_ncols = Max(fc_ncolumns1, fc_ncolumns2);
	fc_my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
	if (fc_my_extra == NULL ||
		fc_my_extra->ncolumns < fc_ncols)
	{
		fcinfo->flinfo->fn_extra =
			MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
							   offsetof(RecordCompareData, columns) +
							   fc_ncols * sizeof(ColumnCompareData));
		fc_my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
		fc_my_extra->ncolumns = fc_ncols;
		fc_my_extra->record1_type = InvalidOid;
		fc_my_extra->record1_typmod = 0;
		fc_my_extra->record2_type = InvalidOid;
		fc_my_extra->record2_typmod = 0;
	}

	if (fc_my_extra->record1_type != fc_tupType1 ||
		fc_my_extra->record1_typmod != fc_tupTypmod1 ||
		fc_my_extra->record2_type != fc_tupType2 ||
		fc_my_extra->record2_typmod != fc_tupTypmod2)
	{
		MemSet(fc_my_extra->columns, 0, fc_ncols * sizeof(ColumnCompareData));
		fc_my_extra->record1_type = fc_tupType1;
		fc_my_extra->record1_typmod = fc_tupTypmod1;
		fc_my_extra->record2_type = fc_tupType2;
		fc_my_extra->record2_typmod = fc_tupTypmod2;
	}

	/* 将元组分解为字段 */
	fc_values1 = (Datum *) palloc(fc_ncolumns1 * sizeof(Datum));
	fc_nulls1 = (bool *) palloc(fc_ncolumns1 * sizeof(bool));
	heap_deform_tuple(&fc_tuple1, fc_tupdesc1, fc_values1, fc_nulls1);
	fc_values2 = (Datum *) palloc(fc_ncolumns2 * sizeof(Datum));
	fc_nulls2 = (bool *) palloc(fc_ncolumns2 * sizeof(bool));
	heap_deform_tuple(&fc_tuple2, fc_tupdesc2, fc_values2, fc_nulls2);

	/*
	 * 扫描对应的列，允许在两个行的不同位置有丢弃列。
	 * i1 和 i2 是物理列索引，j 是
	 * 逻辑列索引。
	 */
	fc_i1 = fc_i2 = fc_j = 0;
	while (fc_i1 < fc_ncolumns1 || fc_i2 < fc_ncolumns2)
	{
		Form_pg_attribute fc_att1;
		Form_pg_attribute fc_att2;

		/*
		 * 跳过丢弃的列
		 */
		if (fc_i1 < fc_ncolumns1 && TupleDescAttr(fc_tupdesc1, fc_i1)->attisdropped)
		{
			fc_i1++;
			continue;
		}
		if (fc_i2 < fc_ncolumns2 && TupleDescAttr(fc_tupdesc2, fc_i2)->attisdropped)
		{
			fc_i2++;
			continue;
		}
		if (fc_i1 >= fc_ncolumns1 || fc_i2 >= fc_ncolumns2)
			break;				/* 我们将在下面的循环中处理不匹配 */

		fc_att1 = TupleDescAttr(fc_tupdesc1, fc_i1);
		fc_att2 = TupleDescAttr(fc_tupdesc2, fc_i2);

		/*
		 * 有两个匹配的列，它们必须是相同类型
		 */
		if (fc_att1->atttypid != fc_att2->atttypid)
			ereport(ERROR,
					(errcode(ERRCODE_DATATYPE_MISMATCH),
					 errmsg("cannot compare dissimilar column types %s and %s at record column %d",
							format_type_be(fc_att1->atttypid),
							format_type_be(fc_att2->atttypid),
							fc_j + 1)));

		/*
		 * 我们认为两个 NULL 相等；NULL > 非 NULL。
		 */
		if (!fc_nulls1[fc_i1] || !fc_nulls2[fc_i2])
		{
			if (fc_nulls1[fc_i1] || fc_nulls2[fc_i2])
			{
				fc_result = false;
				break;
			}

			/* 比较元素对 */
			fc_result = datum_image_eq(fc_values1[fc_i1], fc_values2[fc_i2], fc_att1->attbyval, fc_att2->attlen);
			if (!fc_result)
				break;
		}

		/* 相等，因此继续到下一个列 */
		fc_i1++, fc_i2++, fc_j++;
	}

	/*
	 * 如果我们没有提前退出循环，请检查列计数
	 * 不匹配。 （如果我们发现列值不相等，我们不会报告这种不匹配；这是一种特性还是一个错误？）
	 */
	if (fc_result)
	{
		if (fc_i1 != fc_ncolumns1 || fc_i2 != fc_ncolumns2)
			ereport(ERROR,
					(errcode(ERRCODE_DATATYPE_MISMATCH),
					 errmsg("cannot compare record types with different numbers of columns")));
	}

	pfree(fc_values1);
	pfree(fc_nulls1);
	pfree(fc_values2);
	pfree(fc_nulls2);
	ReleaseTupleDesc(fc_tupdesc1);
	ReleaseTupleDesc(fc_tupdesc2);

	/* 避免在处理已烤制输入时泄漏内存。 */
	PG_FREE_IF_COPY(fc_record1, 0);
	PG_FREE_IF_COPY(fc_record2, 1);

	PG_RETURN_BOOL(fc_result);
}

Datum record_image_ne(PG_FUNCTION_ARGS)
{
	PG_RETURN_BOOL(!DatumGetBool(record_image_eq(fcinfo)));
}

Datum record_image_lt(PG_FUNCTION_ARGS)
{
	PG_RETURN_BOOL(fc_record_image_cmp(fcinfo) < 0);
}

Datum record_image_gt(PG_FUNCTION_ARGS)
{
	PG_RETURN_BOOL(fc_record_image_cmp(fcinfo) > 0);
}

Datum record_image_le(PG_FUNCTION_ARGS)
{
	PG_RETURN_BOOL(fc_record_image_cmp(fcinfo) <= 0);
}

Datum record_image_ge(PG_FUNCTION_ARGS)
{
	PG_RETURN_BOOL(fc_record_image_cmp(fcinfo) >= 0);
}

Datum btrecordimagecmp(PG_FUNCTION_ARGS)
{
	PG_RETURN_INT32(fc_record_image_cmp(fcinfo));
}


/*
 * 行类型哈希函数
 */

Datum hash_record(PG_FUNCTION_ARGS)
{
	HeapTupleHeader fc_record = PG_GETARG_HEAPTUPLEHEADER(0);
	uint32		fc_result = 0;
	Oid			fc_tupType;
	int32		fc_tupTypmod;
	TupleDesc	fc_tupdesc;
	HeapTupleData fc_tuple;
	int			fc_ncolumns;
	RecordCompareData *fc_my_extra;
	Datum	   *fc_values;
	bool	   *fc_nulls;

	check_stack_depth();		/* 对记录类型的列进行递归 */

	/* 从元组中提取类型信息 */
	fc_tupType = HeapTupleHeaderGetTypeId(fc_record);
	fc_tupTypmod = HeapTupleHeaderGetTypMod(fc_record);
	fc_tupdesc = lookup_rowtype_tupdesc(fc_tupType, fc_tupTypmod);
	fc_ncolumns = fc_tupdesc->natts;

	/* 构建临时 HeapTuple 控制结构 */
	fc_tuple.t_len = HeapTupleHeaderGetDatumLength(fc_record);
	ItemPointerSetInvalid(&(fc_tuple.t_self));
	fc_tuple.t_tableOid = InvalidOid;
	fc_tuple.t_data = fc_record;

	/*
	 * 我们安排仅在每一系列调用中查找所需的哈希信息一次，
	 * 假设记录类型不会在我们之下发生变化。
	 */
	fc_my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
	if (fc_my_extra == NULL ||
		fc_my_extra->ncolumns < fc_ncolumns)
	{
		fcinfo->flinfo->fn_extra =
			MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
							   offsetof(RecordCompareData, columns) +
							   fc_ncolumns * sizeof(ColumnCompareData));
		fc_my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
		fc_my_extra->ncolumns = fc_ncolumns;
		fc_my_extra->record1_type = InvalidOid;
		fc_my_extra->record1_typmod = 0;
	}

	if (fc_my_extra->record1_type != fc_tupType ||
		fc_my_extra->record1_typmod != fc_tupTypmod)
	{
		MemSet(fc_my_extra->columns, 0, fc_ncolumns * sizeof(ColumnCompareData));
		fc_my_extra->record1_type = fc_tupType;
		fc_my_extra->record1_typmod = fc_tupTypmod;
	}

	/* 将元组分解为字段 */
	fc_values = (Datum *) palloc(fc_ncolumns * sizeof(Datum));
	fc_nulls = (bool *) palloc(fc_ncolumns * sizeof(bool));
	heap_deform_tuple(&fc_tuple, fc_tupdesc, fc_values, fc_nulls);

	for (int fc_i = 0; fc_i < fc_ncolumns; fc_i++)
	{
		Form_pg_attribute fc_att;
		TypeCacheEntry *fc_typentry;
		uint32		fc_element_hash;

		fc_att = TupleDescAttr(fc_tupdesc, fc_i);

		if (fc_att->attisdropped)
			continue;

		/*
		 * 如果尚未完成，查找哈希函数
		 */
		fc_typentry = fc_my_extra->columns[fc_i].typentry;
		if (fc_typentry == NULL ||
			fc_typentry->type_id != fc_att->atttypid)
		{
			fc_typentry = lookup_type_cache(fc_att->atttypid,
										 TYPECACHE_HASH_PROC_FINFO);
			if (!OidIsValid(fc_typentry->hash_proc_finfo.fn_oid))
				ereport(ERROR,
						(errcode(ERRCODE_UNDEFINED_FUNCTION),
						 errmsg("could not identify a hash function for type %s",
								format_type_be(fc_typentry->type_id))));
			fc_my_extra->columns[fc_i].typentry = fc_typentry;
		}

		/* 计算元素的哈希值 */
		if (fc_nulls[fc_i])
		{
			fc_element_hash = 0;
		}
		else
		{
			LOCAL_FCINFO(fc_locfcinfo, 1);

			InitFunctionCallInfoData(*fc_locfcinfo, &fc_typentry->hash_proc_finfo, 1,
									 fc_att->attcollation, NULL, NULL);
			fc_locfcinfo->args[0].value = fc_values[fc_i];
			fc_locfcinfo->args[0].isnull = false;
			fc_element_hash = DatumGetUInt32(FunctionCallInvoke(fc_locfcinfo));

			/* 我们不期待哈希支持函数返回 null */
			Assert(!fc_locfcinfo->isnull);
		}

		/* 见 hash_array() */
		fc_result = (fc_result << 5) - fc_result + fc_element_hash;
	}

	pfree(fc_values);
	pfree(fc_nulls);
	ReleaseTupleDesc(fc_tupdesc);

	/* 避免在处理已烤制输入时泄漏内存。 */
	PG_FREE_IF_COPY(fc_record, 0);

	PG_RETURN_UINT32(fc_result);
}

Datum hash_record_extended(PG_FUNCTION_ARGS)
{
	HeapTupleHeader fc_record = PG_GETARG_HEAPTUPLEHEADER(0);
	uint64		fc_seed = PG_GETARG_INT64(1);
	uint64		fc_result = 0;
	Oid			fc_tupType;
	int32		fc_tupTypmod;
	TupleDesc	fc_tupdesc;
	HeapTupleData fc_tuple;
	int			fc_ncolumns;
	RecordCompareData *fc_my_extra;
	Datum	   *fc_values;
	bool	   *fc_nulls;

	check_stack_depth();		/* 对记录类型的列进行递归 */

	/* 从元组中提取类型信息 */
	fc_tupType = HeapTupleHeaderGetTypeId(fc_record);
	fc_tupTypmod = HeapTupleHeaderGetTypMod(fc_record);
	fc_tupdesc = lookup_rowtype_tupdesc(fc_tupType, fc_tupTypmod);
	fc_ncolumns = fc_tupdesc->natts;

	/* 构建临时 HeapTuple 控制结构 */
	fc_tuple.t_len = HeapTupleHeaderGetDatumLength(fc_record);
	ItemPointerSetInvalid(&(fc_tuple.t_self));
	fc_tuple.t_tableOid = InvalidOid;
	fc_tuple.t_data = fc_record;

	/*
	 * 我们安排仅在每一系列调用中查找所需的哈希信息一次，
	 * 假设记录类型不会在我们之下发生变化。
	 */
	fc_my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
	if (fc_my_extra == NULL ||
		fc_my_extra->ncolumns < fc_ncolumns)
	{
		fcinfo->flinfo->fn_extra =
			MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
							   offsetof(RecordCompareData, columns) +
							   fc_ncolumns * sizeof(ColumnCompareData));
		fc_my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra;
		fc_my_extra->ncolumns = fc_ncolumns;
		fc_my_extra->record1_type = InvalidOid;
		fc_my_extra->record1_typmod = 0;
	}

	if (fc_my_extra->record1_type != fc_tupType ||
		fc_my_extra->record1_typmod != fc_tupTypmod)
	{
		MemSet(fc_my_extra->columns, 0, fc_ncolumns * sizeof(ColumnCompareData));
		fc_my_extra->record1_type = fc_tupType;
		fc_my_extra->record1_typmod = fc_tupTypmod;
	}

	/* 将元组分解为字段 */
	fc_values = (Datum *) palloc(fc_ncolumns * sizeof(Datum));
	fc_nulls = (bool *) palloc(fc_ncolumns * sizeof(bool));
	heap_deform_tuple(&fc_tuple, fc_tupdesc, fc_values, fc_nulls);

	for (int fc_i = 0; fc_i < fc_ncolumns; fc_i++)
	{
		Form_pg_attribute fc_att;
		TypeCacheEntry *fc_typentry;
		uint64		fc_element_hash;

		fc_att = TupleDescAttr(fc_tupdesc, fc_i);

		if (fc_att->attisdropped)
			continue;

		/*
		 * 如果尚未完成，查找哈希函数
		 */
		fc_typentry = fc_my_extra->columns[fc_i].typentry;
		if (fc_typentry == NULL ||
			fc_typentry->type_id != fc_att->atttypid)
		{
			fc_typentry = lookup_type_cache(fc_att->atttypid,
										 TYPECACHE_HASH_EXTENDED_PROC_FINFO);
			if (!OidIsValid(fc_typentry->hash_extended_proc_finfo.fn_oid))
				ereport(ERROR,
						(errcode(ERRCODE_UNDEFINED_FUNCTION),
						 errmsg("could not identify an extended hash function for type %s",
								format_type_be(fc_typentry->type_id))));
			fc_my_extra->columns[fc_i].typentry = fc_typentry;
		}

		/* 计算元素的哈希值 */
		if (fc_nulls[fc_i])
		{
			fc_element_hash = 0;
		}
		else
		{
			LOCAL_FCINFO(fc_locfcinfo, 2);

			InitFunctionCallInfoData(*fc_locfcinfo, &fc_typentry->hash_extended_proc_finfo, 2,
									 fc_att->attcollation, NULL, NULL);
			fc_locfcinfo->args[0].value = fc_values[fc_i];
			fc_locfcinfo->args[0].isnull = false;
			fc_locfcinfo->args[1].value = Int64GetDatum(fc_seed);
			fc_locfcinfo->args[0].isnull = false;
			fc_element_hash = DatumGetUInt64(FunctionCallInvoke(fc_locfcinfo));

			/* 我们不期待哈希支持函数返回 null */
			Assert(!fc_locfcinfo->isnull);
		}

		/* 见 hash_array_extended() */
		fc_result = (fc_result << 5) - fc_result + fc_element_hash;
	}

	pfree(fc_values);
	pfree(fc_nulls);
	ReleaseTupleDesc(fc_tupdesc);

	/* 避免在处理已烤制输入时泄漏内存。 */
	PG_FREE_IF_COPY(fc_record, 0);

	PG_RETURN_UINT64(fc_result);
}
